package com.terrapages.wmsc4j.client.mosaic;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.lisasoft.patterns.producerconsumer.ProductionQueue;
import com.terrapages.wmsc4j.bean.BBox;
import com.terrapages.wmsc4j.bean.Pos;
import com.terrapages.wmsc4j.client.mosaic.prodcon.Pair;
import com.terrapages.wmsc4j.client.mosaic.prodcon.PlacableImage;
import com.terrapages.wmsc4j.client.mosaic.prodcon.TileImageConsumer;
import com.terrapages.wmsc4j.client.mosaic.prodcon.TileImageProducer;
import com.terrapages.wmsc4j.layer.AbstractLayer;
import com.terrapages.wmsc4j.layer.resolution.Resolution;
import com.terrapages.wmsc4j.layer.tile.Tile;

/**
 * Constructs a abitary sized image of a map by retrieving all tiles from a wmsc layer
 * with-in a given bounding box and patching them together.
 * 
 */
public class MosaicWMSCClient {
	private static Logger log = Logger.getLogger(MosaicWMSCClient.class);
	
	private int numProducers = 5;
	private int numConsumers = 1;
	
	/**
	 * Will create a mosaic map using a layer, bounding box and image size.
	 * Due to the tilecache only caching certain zoom levels the boundingbox requested can not be given. 
	 * The center coordinate of the bbox is garanteed to match the center coodinate of the image
	 * and image size are garanteed to be as requested.
	 * However the actual boundingbox of the image will be the closest match. Thus you may see, on the image, more OR LESS than requested.
	 * The {@link MosaicWMSCResponse} will give the new Bounding box, along with the Image to aid further image minipulation.
	 * 
	 * @param wmsRequest Takes in one standard wms request, 
	 * to a tilecache, without the limitations on image size and boundingbox
	 * 
	 * @return MosaicWMSCResponse containing a BufferedImage and the new Boundingbox
	 */
	public MosaicWMSCResponse getMap(AbstractLayer layer, final BBox bbox,Dimension imageSize){

		//New Image information
		final BufferedImage retImage = new BufferedImage(imageSize.width,imageSize.height,BufferedImage.TYPE_INT_RGB);
		final int zoom = layer.getResolutions().getIndex(new Resolution(bbox,imageSize));
		final Resolution resolution = layer.getResolutions().elementAt(zoom);
		final Pos centre = bbox.getCentrePos();
		final BBox newBoundingBox = determineBoundingBox(centre, imageSize, resolution);
		
		final Tile topLeftTile = new Tile(layer, new Pos(newBoundingBox.getMinLongitude(),newBoundingBox.getMaxLatitude()),resolution);
		final BBox topLeftBox = topLeftTile.getBoundingBox();
		
		//Drawing stuff
		Graphics graphics = retImage.getGraphics();
		//Offset drawing to compensate for first tile not lining up with the edge of the image
		int xTranslation = -1*(int) Math.round((newBoundingBox.getMinLongitude() - topLeftBox.getMinLongitude())/resolution.getValue());
		int yTranslation = -1*(int) Math.round((topLeftBox.getMaxLatitude() - newBoundingBox.getMaxLatitude())/resolution.getValue());
		graphics.translate(xTranslation, yTranslation);
		
		/*Iterate though each tile and create a set of URLs to request 
		  and the point at which to place them on the main image*/
		AbstractLayer area = layer.getSubCoverageLayer(newBoundingBox, zoom, zoom);
		
		List<Pair<Tile,Point>> tiles = new ArrayList<Pair<Tile,Point>>();
		
		for(Tile tile:area)
		{
			Point placement = new Point(
					(tile.getXIndex() - topLeftTile.getXIndex()) * layer.getFormat().getTileSize().width,
					(topLeftTile.getYIndex() - tile.getYIndex()) * layer.getFormat().getTileSize().height
			);
			tiles.add(new Pair<Tile, Point>(tile,placement));
		}
		
		log.debug("Requesting " + tiles.size() + " tiles to make map.");
		
		//Create the producers to request tiles from tilecache
		//   and the consumers to draw them to the main image
		ProductionQueue<PlacableImage> queue = new ProductionQueue<PlacableImage>();
		for(int i =0; i<numProducers; i++)
		{
			queue.addProducer(new TileImageProducer(tiles));
		}
		for(int i =0; i<numConsumers; i++)
		{
			queue.addConsumer(new TileImageConsumer(graphics));
		}
		queue.start();
		
		//wait till the producers and consumers are finished
		while(queue.isRunning()) {
			try{
				Thread.sleep(100);
			}catch (InterruptedException e) {}
		}
		
		//return the new MapImage
		return new MosaicWMSCResponse(retImage,newBoundingBox);
	}
	public final int getNumConsumers() {
		return numConsumers;
	}
	public final void setNumConsumers(final int numConsumers) {
		if(numConsumers <= 0)
		{
			throw new IllegalArgumentException("There must be at least one consumer");
		}
		this.numConsumers = numConsumers;
	}
	public final int getNumProducers() {
		return numProducers;
	}
	public final void setNumProducers(final int numProducers) {
		if(numProducers <= 0)
		{
			throw new IllegalArgumentException("There must be at least one producer");
		}
		this.numProducers = numProducers;
	}
	
	
	/**
	 * Find a Bounding box that has the centre at the given center,
	 * that spans the spatial distance of ImageSize * Resolution
	 * (ImageSize in Pixels, Resolution in Degrees per pixel)
	 * 
	 * @param centre The center of the wanted bounding box
	 * @param imageSize The number of pixels wide and high
	 * @param resolution The amount of degrees per pixel there are.
	 * @return
	 */
	private static BBox determineBoundingBox(final Pos centre, final Dimension imageSize, final Resolution resolution)
	{
		final BBox retBBox = new BBox(
				centre.getLongitude() - resolution.getValue() * imageSize.getWidth()/2,
				centre.getLatitude()  - resolution.getValue() * imageSize.getHeight()/2,
				centre.getLongitude() + resolution.getValue() * imageSize.getWidth()/2,
				centre.getLatitude()  + resolution.getValue() * imageSize.getHeight()/2
		);
		return retBBox;
	}
}
